﻿namespace WebPacker.Tests.Packs.Builders
{
	using System;
	using System.Collections;
	using System.IO;
	using System.Linq;
	using NUnit.Framework;
	using WebPacker.Configuration;
	using WebPacker.Filters;
	using WebPacker.Packs;
	using WebPacker.Packs.Builders;
	using WebPacker.Packs.Resources;
	using WebPacker.Packs.Watchers;
	using WebPacker.VersionGenerators;

	[TestFixture]
	public class PackBuilderTest
	{
		public void ConstructorEmptyTest()
		{
			var packBuilder = new PackBuilder();
			Assert.IsNotNull(packBuilder.Filters);
			Assert.IsNotNull(packBuilder.VersionGenerators);
			Assert.IsTrue(packBuilder.Filters.Count == 0);
			Assert.IsTrue(packBuilder.VersionGenerators.Count == 0);
		}

		[Test]
		public void ConstructorTest()
		{
			var filters = new IFilter[] { new YuiCssMinifier(), new YuiJavascriptMinifier() };
			var versionGenerators = new IVersionGenerator[] { new LastModifiedVersionGenerator(), new ManualVersionGenerator() };
			var watcher = new CompositeWatcher();
			var packBuilder = new PackBuilder(filters, watcher, versionGenerators);
			Assert.IsNotNull(packBuilder.Filters);
			Assert.IsNotNull(packBuilder.VersionGenerators);
			Assert.That(packBuilder.Filters, Is.EquivalentTo(filters));
			Assert.That(packBuilder.VersionGenerators, Is.EquivalentTo(versionGenerators));
		}

		[Test]
		public void GeneratePackName1Test()
		{
			var packBuilder = GetPackBuilder();
			var expected = "432c5e04a0e86f7d2920c56b132779f0";
			var actual = packBuilder.GeneratePackName(PackType.Css, new string[] { "1.css", "2.css" });
			Assert.AreEqual(expected, actual);
		}

		[Test]
		public void GeneratePackName2Test()
		{
			var packBuilder = GetPackBuilder();
			var name1 = packBuilder.GeneratePackName(PackType.Css, new string[] { "1.css", "2.css" });
			var name2 = packBuilder.GeneratePackName(PackType.Css, new string[] { "2.css", "1.css" });
			Assert.AreNotEqual(name1, name2);
		}

		[Test]
		public void GeneratePackName3Test()
		{
			var packBuilder = GetPackBuilder();
			var name1 = packBuilder.GeneratePackName(PackType.Css, new string[] { "1.css", "2.css" });
			var name2 = packBuilder.GeneratePackName(PackType.JavaScript, new string[] { "1.css", "2.css" });
			Assert.AreNotEqual(name1, name2);
		}

		[Test]
		public void BuildPack1Test([Values(true, false)] bool byResources)
		{
			var packBuilder = GetPackBuilder();

			var packInfo = new PackInfo
			{
				PackType = PackType.JavaScript,
				FilterName = "js",
				VersionGeneratorName = "lastModified",
				Name = "test1.js",
				Resources = new ResourceInfo[] 
				{
					new ResourceInfo
					{
						Name = "1.js"
					},
					new ResourceInfo
					{
						Name = "2.js"
					}
				}
			};

			if (byResources)
			{
				PreparePackInfoByResources(packInfo);
			}

			CheckPack(packBuilder, packInfo);
		}

		[Test]
		public void BuildPack2Test([Values(true, false)] bool byResources)
		{
			var packBuilder = GetPackBuilder();

			var packInfo = new PackInfo
			{
				PackType = PackType.Css,
				BasePath = "styles",
				FilterName = "css",
				Name = "test2.css",
				Resources = new ResourceInfo[] 
				{
					new ResourceInfo
					{
						Name = "main.css"
					},
					new ResourceInfo
					{
						Name = "index.css"
					}
				}
			};

			if (byResources)
			{
				PreparePackInfoByResources(packInfo);
			}

			CheckPack(packBuilder, packInfo);
		}

		[Test]
		public void BuildPack3Test([Values(true, false)] bool byResources)
		{
			var packBuilder = GetPackBuilder();

			var packInfo = new PackInfo
			{
				PackType = PackType.JavaScript,
				BasePath = "samples",
				FilterName = null,
				Version = "1.2.3",
				Name = "test3.js",
				VersionGeneratorName = "md5",
				Resources = new ResourceInfo[] 
				{
					new ResourceInfo
					{
						Name = "test1.js"
					}
				}
			};

			if (byResources)
			{
				PreparePackInfoByResources(packInfo);
			}

			CheckPack(packBuilder, packInfo);
		}

		[Test]
		public void BuildPackResourceErrorTest()
		{
			var packBuilder = GetPackBuilder();

			Assert.Throws<ArgumentNullException>(() =>
			{
				packBuilder.BuildPack(PackType.JavaScript, new string[] { "1.js", null, "2.js" });
			});
		}

		[Test]
		public void BuildPackFilterErrorTest()
		{
			var packBuilder = GetPackBuilder();

			var packInfo = new PackInfo
			{
				PackType = PackType.JavaScript,
				BasePath = "scripts",
				FilterName = "UnknownFilter",
				Name = "test3.js",
				Resources = new ResourceInfo[] 
				{
					new ResourceInfo
					{
						Name = "script.js"
					}
				}
			};

			Assert.Throws<ArgumentException>(() =>
			{
				packBuilder.BuildPack(packInfo);
			});
		}

		[Test]
		public void BuildPackVersionGeneratorErrorTest()
		{
			var packBuilder = GetPackBuilder();

			var packInfo = new PackInfo
			{
				PackType = PackType.JavaScript,
				BasePath = "scripts",
				FilterName = "js",
				VersionGeneratorName = "unknownVersionGenerator",
				Name = "test3.js",
				Resources = new ResourceInfo[] 
				{
					new ResourceInfo
					{
						Name = "script.js"
					}
				}
			};

			Assert.Throws<ArgumentException>(() =>
			{
				packBuilder.BuildPack(packInfo);
			});
		}

		[Test]
		public void BuildPackNameErrorTest()
		{
			var packBuilder = GetPackBuilder();

			var packInfo = new PackInfo
			{
				PackType = PackType.JavaScript,
				BasePath = "scripts",
				FilterName = "js",
				Name = null,
				VersionGeneratorName = "md5",
				Resources = new ResourceInfo[] 
				{
					new ResourceInfo
					{
						Name = "script.js"
					}
				}
			};

			Assert.Throws<ArgumentNullException>(() =>
			{
				packBuilder.BuildPack(packInfo);
			});
		}

		private IPackBuilder GetPackBuilder()
		{
			var packBuilder = new PackBuilder();
			packBuilder.Filters.Add(new YuiCssMinifier { Name = "css" });
			packBuilder.Filters.Add(new YuiJavascriptMinifier { Name = "js" });
			packBuilder.VersionGenerators.Add(new ManualVersionGenerator() { Name = "manual" });
			packBuilder.VersionGenerators.Add(new LastModifiedVersionGenerator() { Name = "lastModified" });
			packBuilder.VersionGenerators.Add(new Md5VersionGenerator() { Name = "md5" });
			return packBuilder;
		}

		private void PreparePackInfoByResources(PackInfo packInfo)
		{
			packInfo.Name = GetPackBuilder().GeneratePackName(packInfo.PackType, packInfo.Resources.Select(x => x.Name).ToArray());
		}

		private void CheckPack(IPackBuilder packBuilder, PackInfo packInfo)
		{
			var pack = packBuilder.BuildPack(packInfo) as Pack;

			Assert.IsNotNull(pack);
			Assert.AreEqual(packInfo.Name, pack.Name);
			Assert.AreEqual(packInfo.PackType, pack.PackType);

			var expectedFilter = packInfo.FilterName;

			if (expectedFilter == null)
			{
				expectedFilter = ((PackBuilder)packBuilder).Filters.Where(x => x.PackType == packInfo.PackType).Select(x => x.Name).First();
			}

			Assert.IsNotNull(pack.Filter);
			Assert.AreEqual(expectedFilter, pack.Filter.Name);

			if (packInfo.VersionGeneratorName != null)
			{
				Assert.AreEqual(packInfo.VersionGeneratorName, pack.VersionGenerator.Name);
				Assert.AreEqual(pack.VersionGenerator.GetNextVersion(pack), pack.Version);
			}
			else
			{
				Assert.IsNull(pack.VersionGenerator);
				Assert.AreEqual(packInfo.Version ?? string.Empty, pack.Version);
			}

			Assert.IsFalse(pack.Monitoring);
			Assert.That(pack.Resources, Is.EquivalentTo(packInfo.Resources).Using(new ResourceAndResourceInfoEqualityComparer(packInfo.BasePath)));
		}

		private class ResourceAndResourceInfoEqualityComparer : IEqualityComparer
		{
			private readonly string basePath;

			public ResourceAndResourceInfoEqualityComparer(string basePath)
			{
				this.basePath = basePath;
			}

			public new bool Equals(object x, object y)
			{
				if (x == null || y == null)
				{
					return x == y;
				}

				IResource resource;
				ResourceInfo resourceInfo;

				if (x is ResourceInfo && y is IResource)
				{
					resource = (IResource)y;
					resourceInfo = (ResourceInfo)x;
				}
				else if (x is IResource && y is ResourceInfo)
				{
					resource = (IResource)x;
					resourceInfo = (ResourceInfo)y;
				}
				else
				{
					return false;
				}

				var name = basePath != null ? Path.Combine(basePath, resourceInfo.Name) : resourceInfo.Name;
				return resource.Name == name;
			}

			public int GetHashCode(object obj)
			{
				return obj.GetHashCode();
			}
		}
	}
}
